﻿namespace Trading.IBApi

open System
open System.Collections.Generic
open System.Reactive.Linq
open System.Linq
open System.Diagnostics
open System.Reactive.Subjects
open System.Reactive.Disposables

open com.ib.client

open Trading

open DateTimeExtensions
open ContractExtensions


type RequestId = int

type JSharpClient(host, port, clientId) = 
    
    //-------------------- Subjects --------------------

    let ticksSubject = new Subject<RequestId * Tick>()
    let nextTick = curry ticksSubject.OnNext 

    let errorsSubject = new Subject<string>()
    let contractDetailsSubject = new Subject<RequestId * ContractDetails option>()
    let availableOrderIdsSubject = new BehaviorSubject<int>(0)

    let historicalDataSubject = new Subject<RequestId * PriceBar option>()
    let realTimeBarsSubject = new Subject<RequestId * RealTimeBar>()

    //-------------------- Callback wrapper --------------------

    let callBacksWrapper = 
        {
            new EWrapperBase() with 

                //AnyWrapper Interface methods
                member this.error(message : string) = AnyWrapperMsgGenerator.error(message) |> errorsSubject.OnNext
                member this.error(why : java.lang.Exception) = AnyWrapperMsgGenerator.error(why) |> errorsSubject.OnNext
                member this.error(id, code, message) = AnyWrapperMsgGenerator.error(id, code, message) |> errorsSubject.OnNext

                // EWrapper Interface methods
                
                //RealTime Bars
                member this.realtimeBar(reqId, date, open_, high, low, close, volume, wap, count) =

                    let nextBar =
                        {
                            Date = DateTime.FromUnixTimeLocal (date.ToString())
                            Open = open_
                            High = high
                            Low = low
                            Close = close
                            Volume = int volume
                            WAP = wap
                            Count = count
                        }
                    realTimeBarsSubject.OnNext(reqId, nextBar)

                // Historical Market Data
                member this.historicalData(reqId, date, open_, high, low, close, volume, count, wap, hasGaps) =

                    let nextBar = 
                        if date.StartsWith "finished" then
                            None
                        else
                            {
                                Date = DateTime.FromUnixTime date
                                Open = open_
                                High = high
                                Low = low
                                Close = close
                                Volume = volume
                                Count = count
                                WAP = wap
                                HasGaps = hasGaps
                            } |> Some
                    historicalDataSubject.OnNext(reqId, nextBar)

                //Market data
                member this.tickPrice(tickerId, tickType, value, canAutoExecute) = 
                    match tickType with
                    | TickType.LAST -> LastPrice value
                    | TickType.ASK -> AskPrice value
                    | TickType.BID -> BidPrice value
                    | _ -> tickType |> string |> Unknown 

                    |> nextTick tickerId

                member this.tickSize(tickerId, tickType, value) = 
                    match tickType with
                    | TickType.LAST_SIZE -> LastSize value
                    | TickType.ASK_SIZE -> AskSize value
                    | TickType.BID_SIZE -> BidSize value
                    | TickType.VOLUME -> Volume value
                    | _ -> tickType |> string |> Unknown 

                    |> nextTick tickerId


                member this.tickString(tickerId, tickType, value) = 
                    match tickType with
                    | TickType.LAST_TIMESTAMP -> value |> DateTime.FromUnixTime |> LastTimestamp
                    | _ -> tickType |> string |> Unknown 

                    |> nextTick tickerId

                // Contract Details
                member this.contractDetails(reqId, contractDetails) = contractDetailsSubject.OnNext(reqId, Some contractDetails) 
                member this.contractDetailsEnd(reqId) = contractDetailsSubject.OnNext(reqId, None) 

                //Order managment
                member this.nextValidId(orderId) = availableOrderIdsSubject.OnNext(orderId)
        }

    let client = EClientSocket(callBacksWrapper)
    do client.eConnect(host, port, clientId)

    let nextRequestId = 
        let currentTickerId = ref 0
        fun () -> 
            incr currentTickerId
            !currentTickerId

    let nextOrderId() = 
        client.reqIds 1
        async {
            let! ids = availableOrderIdsSubject.Take(1) |> Async.AwaitObservable
            return ids.Single()
        }

    interface IDisposable with 
        member this.Dispose() = client.eDisconnect()

    interface IBrokerGateway with 
        member this.Errors = errorsSubject.AsObservable().Do(box >> Debug.WriteLine)
        member this.ConnectionInfo = 
            sprintf "Connection time: %s. Server version: %i." (client.TwsConnectionTime()) (client.serverVersion())

        member this.Ticks contract =
            {
                new IObservable<_> with
                    member self.Subscribe(observer) =
                        let reqId = nextRequestId()
                        let ticks = 
                            ticksSubject.AsObservable() 
                            |> Observable.filter (fst >> reqId.Equals)
                            |> Observable.map snd 
                            |> Observable.filter (fun x -> match x with | Tick.Unknown _ -> false | _ -> true)
                        ticks.Subscribe(observer) |> ignore
                        client.reqMktData(reqId , contract, genericTickList = null, snapshot = false)
                        Disposable.Create(fun() -> client.cancelMktData(reqId))
            }
               

        member this.RealTimeBars(contract, whatToShow, useRTH) =
            let useRTH' =  defaultArg useRTH 1
            let useRTHb = useRTH' = 1
                
            {
                new IObservable<_> with
                    member self.Subscribe(observer) =
                        let reqId = nextRequestId()
                        let bars = 
                            realTimeBarsSubject.AsObservable()
                                |> Observable.filter (fst >> reqId.Equals)
                                |> Observable.map snd
                        bars.Subscribe(observer) |> ignore
                        client.reqRealTimeBars(reqId, contract, barSize=5, whatToShow=whatToShow, useRTH=useRTHb) // The only valid span is 5sec
                        Disposable.Create(fun() -> client.cancelRealTimeBars(reqId))
            }

        member this.HistoricalData(contract, endDateTime, span, barSize, whatToShow, useRTH) =
            let endDateTime' = endDateTime.ToString("yyyyMMdd HH:mm:ss");
            let span' = sprintf "%d %O" <|| span
            let useRTH' =  defaultArg useRTH 1

            {
                new IObservable<_> with
                    member self.Subscribe(observer) =
                        let reqId = nextRequestId()
                        let bars = 
                            historicalDataSubject.AsObservable()
                                |> Observable.filter (fst >> reqId.Equals)
                                |> Observable.map snd
                                |> Observable.takeWhile Option.isSome 
                                |> Observable.map Option.get
                        bars.Subscribe(observer) |> ignore
                        client.reqHistoricalData(reqId, contract, endDateTime', span', string barSize, whatToShow, useRTH', formatDate = 2)
                        Disposable.Create(fun() -> client.cancelHistoricalData(reqId))
            }
            |> Async.AwaitObservable

        member this.ContractDetails mapping contract =
            let reqId = nextRequestId()
            client.reqContractDetails(reqId, contract)
            contractDetailsSubject.AsObservable()
                |> Observable.filter (fst >> reqId.Equals)
                |> Observable.map snd
                |> Observable.takeWhile Option.isSome 
                |> Observable.map (Option.get >> mapping)
                |> Async.AwaitObservable

        member this.PlaceOrder(contract, order) = 
            async {
                let! orderId = nextOrderId()
                client.placeOrder(orderId, contract, order)
            }

[<AutoOpen>]
module IBrokerGatewayExtensions = 
    type IBrokerGateway with 
        member this.OptionStrikes = this.ContractDetails (fun c -> c.m_summary.m_strike)

        member this.ContractId contract = 
            async {
                let! result = this.ContractDetails (fun x -> x.m_summary.m_conId) contract
                return result.Single()
            }

        member this.CreateComboLeg(contract, action : ActionSide, ?ratio, ?exchange, ?openClose) =
            async { 
                let! contractId = this.ContractId contract
                return ComboLeg(
                    p_conId = contractId,
                    p_ratio = defaultArg ratio 1,
                    p_action = sprintf "%A" action,        
                    p_exchange = defaultArg exchange "SMART",
                    p_openClose = defaultArg openClose 0
                )
            }

